//----------------------------------------------------------------------
//
//	XFILE, XFILEREAD, XFILEWRITE
//
//	output classes that read and write compressed files
//
//----------------------------------------------------------------------
#include "Globals.h"
#include "zlib/zlib114/zlib.h"
#include "xfile.h"
#include <string.h>

//----------------------------------------------------------------------
//----------------------------------------------------------------------
//----------------------------------------------------------------------
//----------------------------------------------------------------------
//	alloc and free functions
//	used by ZLIB and internally
//----------------------------------------------------------------------
//----------------------------------------------------------------------
//----------------------------------------------------------------------
//----------------------------------------------------------------------
#include <malloc.h>

static	void	*XFILE_allocfn( void *context, u32 size, u32 nItems )
{
	return malloc(size*nItems);
}

static	void	XFILE_freefn( void *context, void *ptr )
{
	if( ptr )
		free(ptr);
}

//----------------------------------------------------------------------
//----------------------------------------------------------------------
//----------------------------------------------------------------------
//----------------------------------------------------------------------
//	XFILE implementation
//----------------------------------------------------------------------
//----------------------------------------------------------------------
//----------------------------------------------------------------------
//----------------------------------------------------------------------
XFILE::~XFILE()
{
	// close must be called from the derived destructor
	// because it makes a virtual call...
//	Close();
}

//----------------------------------------------------------------------
//	close the file
//----------------------------------------------------------------------
void XFILE::Close()
{
	CompressInit(false);
	if( hFile != INVALID_HANDLE_VALUE )
	{
		//fclose(fp);
		//fp = 0;
		CloseHandle( hFile );
		hFile = INVALID_HANDLE_VALUE;
	}
}

//----------------------------------------------------------------------
//	is the file currently open?
//----------------------------------------------------------------------
bool XFILE::IsOpen()
{
	return (hFile != INVALID_HANDLE_VALUE && pZlib);
}

//----------------------------------------------------------------------
//	initialize the compressor
//----------------------------------------------------------------------
bool XFILE::CompressInit( bool init )
{
	bool	ret = false;

	if( init )
	{
		if( pZlib==0 )	// already initialized?!?!
		{
			// allocate memory
			pZlib = (z_stream*)XFILE_allocfn(0,sizeof(*pZlib),1);
			if( pZlib )
			{
				// initial setup
				memset(pZlib,0,sizeof(z_stream));
				pZlib->zalloc = XFILE_allocfn;
				pZlib->zfree  = XFILE_freefn;

				// allocate compression buffer
				compressBuffer = (u8*)XFILE_allocfn(0,compressBufferSize,1);
				if( compressBuffer )
				{
					// setup the compressor for inflate or deflate
					ret = CompressSetup(true);
				}

				// did anything fail?
				if( !ret )
					CompressInit(false);
			}
		}
	}
	else
	{
		ret = true;
		if( pZlib )
		{
			// close down the compressor
			CompressSetup(false);

			// free the compression buffer
			if( compressBuffer )
			{
				XFILE_freefn(0,compressBuffer);
				compressBuffer = 0;
			}

			// free the compressor
			XFILE_freefn(0,pZlib);
			pZlib = 0;
		}
	}

	return ret;
}

//----------------------------------------------------------------------
//	flush the file (no functionality needed)
//----------------------------------------------------------------------
void XFILE::Flush()
{
}

//----------------------------------------------------------------------
//	do any file IO needed
//----------------------------------------------------------------------
bool XFILE::DoIO()
{
	return false;
}

//----------------------------------------------------------------------
//----------------------------------------------------------------------
//----------------------------------------------------------------------
//----------------------------------------------------------------------
//	XFILEREAD implementation
//----------------------------------------------------------------------
//----------------------------------------------------------------------
//----------------------------------------------------------------------
//----------------------------------------------------------------------
XFILEREAD::XFILEREAD()
{
}
XFILEREAD::~XFILEREAD()
{
	Close();
}

//----------------------------------------------------------------------
//	get the # of bytes read
//----------------------------------------------------------------------
u32 XFILEREAD::GetTrafficBytes( bool compressed )
{
	if( !IsOpen() )
		return 0;
	return compressed?pZlib->total_in:pZlib->total_out;
}

//----------------------------------------------------------------------
//	open a file for reading
//----------------------------------------------------------------------
bool XFILEREAD::Open( const char *filename )
{
	Close();

	//fp = fopen(filename,"rb");
	hFile = CreateFileA( filename, GENERIC_READ, NULL, NULL, OPEN_EXISTING, NULL, NULL );
	if( hFile != INVALID_HANDLE_VALUE )
	{
		if( !CompressInit(true) )
			Close();
	}
	return IsOpen();
}

//----------------------------------------------------------------------
//	prepare the compressor
//----------------------------------------------------------------------
bool XFILEREAD::CompressSetup( bool init )
{
	bool	ret = false;
	if( init )
	{
		assert(pZlib);
		assert(compressBuffer);
		if( inflateInit(pZlib)==Z_OK )
		{
			pZlib->next_in   = compressBuffer;
			pZlib->avail_in  = 0;
			pZlib->next_out  = 0;
			pZlib->avail_out = 0;
			ret = true;
		}
	}
	else
	{
		if( pZlib )
			inflateEnd(pZlib);
		ret = true;
	}
	return ret;
}

//----------------------------------------------------------------------
//	do any reading needed
//----------------------------------------------------------------------
bool XFILEREAD::DoIO()
{
	DWORD		count;
	if( !IsOpen() )	return false;

	// is there any compressed data still available?
	if( pZlib->avail_in==0 )
	{
		// no - we are OK to read into compression buffer
		//count = fread(compressBuffer,1,compressBufferSize,fp);
		ReadFile( hFile, compressBuffer, compressBufferSize, &count, NULL );
		if( count==0 )	return false;

		// reset the compressor's buffer
		pZlib->avail_in = count;
		pZlib->next_in  = compressBuffer;
	}
	return true;
}

//----------------------------------------------------------------------
//	skip n bytes
//----------------------------------------------------------------------
bool XFILEREAD::Skip( u32 numBytes, u32 *numBytesRead )
{
	u8			temp[1024];
	u32			toRead;
	u32			wasRead;
	u32			totalRead = 0;
	bool		ret = true;

	while( ret && totalRead!=numBytes )
	{
		toRead = Min(numBytes,countof(temp));
		ret = Read(temp,toRead,&wasRead);
		totalRead += wasRead;
	}

	if( numBytesRead )
		*numBytesRead = totalRead;
	return ret;
}


//----------------------------------------------------------------------
//	read n bytes
//----------------------------------------------------------------------
bool XFILEREAD::Read( void *ptr, u32 numBytes, u32 *numBytesRead )
{
	bool	ret = false;
	u32		count = 0;
	int		err;

	if( IsOpen() )
	{
		if( ptr==0 )
			return Skip(numBytes,numBytesRead);

		// setup our output buffer
		pZlib->next_out = (Bytef*)ptr;
		pZlib->avail_out = numBytes;

		// while there is space in the output buffer
		while( pZlib->avail_out )
		{
			// if compression buffer is empty, read from disk
			if( pZlib->avail_in==0 && !DoIO() )
				break;

			// decompress
			err = inflate(pZlib,Z_NO_FLUSH);
			if( err!=Z_OK && err!=Z_STREAM_END )
				break;	// error!!!
		}
		ret = (pZlib->avail_out==0);

		// calculate the number of bytes read
		count = numBytes - pZlib->avail_out;
	}

	if( numBytesRead )
		*numBytesRead = count;
	
	return ret;
}

//----------------------------------------------------------------------
//----------------------------------------------------------------------
//----------------------------------------------------------------------
//----------------------------------------------------------------------
//	XFILEWRITE implementation
//----------------------------------------------------------------------
//----------------------------------------------------------------------
//----------------------------------------------------------------------
//----------------------------------------------------------------------
XFILEWRITE::XFILEWRITE()
{
}
XFILEWRITE::~XFILEWRITE()
{
	Close();
}

//----------------------------------------------------------------------
//	get the # of bytes read
//----------------------------------------------------------------------
u32 XFILEWRITE::GetTrafficBytes( bool compressed )
{
	if( !IsOpen() )
		return 0;
	return compressed?pZlib->total_out:pZlib->total_in;
}

//----------------------------------------------------------------------
//	open a file for writing
//----------------------------------------------------------------------
bool XFILEWRITE::Open( const char *filename )
{
	Close();

	//fp = fopen(filename,"wb");
	hFile = CreateFileA( filename, GENERIC_WRITE, NULL, NULL, NULL, NULL, NULL );
	if( hFile != INVALID_HANDLE_VALUE )
	{
		if( !CompressInit(true) )
			Close();
	}
	return IsOpen();
}

//----------------------------------------------------------------------
//	prepare the compressor
//----------------------------------------------------------------------
bool XFILEWRITE::CompressSetup( bool init )
{
	bool	ret = false;
	if( init )
	{
		assert(pZlib);
		assert(compressBuffer);

		if( deflateInit(pZlib,Z_BEST_COMPRESSION)==Z_OK )
		{
			pZlib->next_in   = 0;
			pZlib->avail_in  = 0;
			pZlib->next_out  = compressBuffer;
			pZlib->avail_out = compressBufferSize;
			ret = true;
		}
	}
	else
	{
		if( pZlib )
		{
			Flush();
			deflateEnd(pZlib);
		}
		ret = true;
	}
	return ret;
}

//----------------------------------------------------------------------
//	flush the output file
//----------------------------------------------------------------------
void XFILEWRITE::Flush()
{
	if( IsOpen() )
	{
		assert(pZlib->avail_in==0);
		while( deflate(pZlib,Z_FINISH)==Z_OK )	// until returns Z_STREAM_END
			DoIO();	// write out
		DoIO();	// write out last bit
	}
}

//----------------------------------------------------------------------
//	do any writing needed
//----------------------------------------------------------------------
bool XFILEWRITE::DoIO()
{
	u32		count;
	if( !IsOpen() )	return false;

	// figure out how much data there is to write out
	count = compressBufferSize - pZlib->avail_out;
	if( count!=0 )
	{
		// write the data out
		//fwrite(compressBuffer,1,count,fp);
		DWORD dwWritten;
		WriteFile( hFile, compressBuffer, count, &dwWritten, NULL );

		// reset the compression buffer
		pZlib->next_out  = compressBuffer;
		pZlib->avail_out = compressBufferSize;
	}
	return true;
}

//----------------------------------------------------------------------
//	write to the compressed file
//----------------------------------------------------------------------
bool XFILEWRITE::Write( const void *from, u32 length, u32 *bytesWritten )
{
	u32		count = 0;
	int		err;
	bool	ret = false;


	if( IsOpen() )
	{
		ret = true;

		// compress from "from" into compression buffer
		// write out compression buffer when full
		pZlib->next_in = (Bytef*)from;
		pZlib->avail_in = length;
		while( pZlib->avail_in && ret)
		{
			// compress
			if( (err=deflate(pZlib,Z_NO_FLUSH))!=Z_OK )
			{
				ret = false;
			}
			// if compression buffer is full, write it to disk
			else if( pZlib->avail_out==0 && !DoIO() )
			{
				ret = false;
			}
		}

		count = length - pZlib->avail_in;
	}

	if( bytesWritten )
		*bytesWritten = count;

	return ret;
}

//----------------------------------------------------------------------
//	write a repeated byte to the compressed file
//----------------------------------------------------------------------
bool	XFILEWRITE::WriteByte( u8 byteToWrite, u32 numBytes, u32 *numBytesWritten )
{
	bool	ret = true;
	u8		buffer[64];
	u32		bytesToWrite;
	u32		written;

	memset(buffer,byteToWrite,countof(buffer));

	bytesToWrite = numBytes;
	while( ret && bytesToWrite )
	{
		ret = Write(buffer,Min(countof(buffer),bytesToWrite),&written);
		bytesToWrite -= written;
	}

	if( numBytesWritten )
		numBytesWritten[0] = numBytes - bytesToWrite;

	return ret;
}
